﻿using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Xml.Linq;
using DACU.Tools;
using DACU.VkontakteApi.Enums;

namespace DACU.VkontakteApi.LongPoll
{
	public class VKLongPollServer
	{
		public class RequestState
		{
			// This class stores the State of the request.
			//new const int BufferSize = 1024;
			public StringBuilder RequestData;
			public byte[] BufferRead;
			public HttpWebRequest Request;
			public HttpWebResponse Response;
			public Stream StreamResponse;

			public RequestState()
			{
				BufferRead = new byte[BufferSize];
				RequestData = new StringBuilder("");
				Request = null;
				StreamResponse = null;
			}
		}

		public static ManualResetEvent AllDone = new ManualResetEvent(false);
		const int BufferSize = 1024;
		const int DefaultTimeout = 2 * 60 * 1000; // 2 minutes timeout

		private string _server;
		/// <summary>
		/// server - адрес сервера к которому нужно отправлять запрос
		/// </summary>
		public string Server
		{
			get { return _server; }
		}

		private string _key;
		/// <summary>
		/// key - секретный ключ сессии
		/// </summary>
		public string Key
		{
			get { return _key; }
		}

		private string _ts;
		/// <summary>
		/// ts - номер последнего события, начиная с которого Вы хотите получать данные
		/// </summary>
		public string Ts
		{
			get { return _ts; }
		}

		void BeginResponse(object obj=null)
		{
			HttpWebRequest request;
			string url = URLTemplates.LongPollServer.FormatStr(Server, Key, Ts);
			try
			{
				request = (HttpWebRequest)WebRequest.Create(url);
				var myRequestState = new RequestState {Request = request};
				var result=request.BeginGetResponse(RespCallback,myRequestState);
				// this line implements the timeout, if there is a timeout, the callback fires and the request becomes aborted
				ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, TimeoutCallback, request, DefaultTimeout, true);
				// The response came in the allowed time. The work processing will happen in the 
				// callback function.
				AllDone.WaitOne();
				// Release the HttpWebResponse resource.
				myRequestState.Response.Close();
			}
			catch
			{
				throw;
			}
		}

		// Abort the request if the timer fires.
		private void TimeoutCallback(object state, bool timedOut)
		{
			if (timedOut)
			{
				var request = state as HttpWebRequest;
				if (request != null)
					request.Abort();
			}
		}

		private void RespCallback(IAsyncResult asynchronousResult)
		{
			try
			{
				// State of request is asynchronous.
				var myRequestState = (RequestState)asynchronousResult.AsyncState;
				HttpWebRequest myHttpWebRequest = myRequestState.Request;
				myRequestState.Response = (HttpWebResponse)myHttpWebRequest.EndGetResponse(asynchronousResult);

				// Read the response into a Stream object.
				Stream responseStream = myRequestState.Response.GetResponseStream();
				myRequestState.StreamResponse = responseStream;

				// Begin the Reading of the contents of the HTML page and print it to the console.
				IAsyncResult asynchronousInputRead = responseStream.BeginRead(myRequestState.BufferRead, 0, BufferSize, ReadCallBack, myRequestState);
				return;
			}
			catch
			{
				AllDone.Set();
				throw;
			}
		}

		private void ReadCallBack(IAsyncResult asyncResult)
		{
			try
			{
				var myRequestState = (RequestState)asyncResult.AsyncState;
				Stream responseStream = myRequestState.StreamResponse;
				int read = responseStream.EndRead(asyncResult);
				if (read > 0)
				{
					myRequestState.RequestData.Append(Encoding.ASCII.GetString(myRequestState.BufferRead, 0, read));
					IAsyncResult asynchronousResult = responseStream.BeginRead(myRequestState.BufferRead, 0, BufferSize, ReadCallBack, myRequestState);
					return;
				}
				else
				{
					if (myRequestState.RequestData.Length > 1)
					ParseResponse(myRequestState.RequestData);
					responseStream.Close();
					AllDone.Set();
				}
			}
			catch
			{
				AllDone.Set();
				throw;
			}
		}

		void ParseResponse(StringBuilder response)
		{
			var str = response.ToString();
			const string ts = "\"ts\":";
			const string updates = ",\"updates\":[";
			const char lastChar = ']';
			if(str.Contains("failed"))
			{
				Get();
				BeginResponse();
				return;
			}
			int index1 = str.IndexOf(ts);
			int index2 = str.IndexOf(updates);
			int index3 = str.LastIndexOf(lastChar);
			if (index1 > -1 && index2 - (index1 + ts.Length) > 0)
				_ts = str.Substring(index1 + ts.Length, index2 - (index1 + ts.Length));
			if(index3-1==index2+updates.Length)
				return;
		}

		public void Get()
		{
			if (!VKApi.IsLogged)
			{
				VKApi.Login();
				Get();
			}
			var resp = VKRequest.MethodResponse(VKMethods.GetLongPollServer);
			XElement elem = XElement.Parse(resp);
			ParseGetMethod(elem);
		}

		void ParseGetMethod(XElement response)
		{
			if (response == null || response.IsEmpty) throw new ArgumentNullException("response");
			_key = XmlUtils.GetNodeString(response.Element("key"));
			_server = XmlUtils.GetNodeString(response.Element("server"));
			_ts = XmlUtils.GetNodeString(response.Element("ts"));
		}

		public void Connect()
		{
			if(String.IsNullOrWhiteSpace(Key)) Get();
			ThreadPool.QueueUserWorkItem(BeginResponse);
		}
	}
}
